/*
 * Copyright 2025 RestfulToolX Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.welson.restfultoolx.service;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.AnnotatedElementsSearch;
import com.welson.restfultoolx.model.ApiInfo;
import com.welson.restfultoolx.model.ParamInfo;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Controller扫描器服务实现
 * 使用PSI API扫描Spring Boot项目中的Controller类和方法
 */
@Service
public final class ControllerScannerService implements ControllerScanner {
    
    private static final String CONTROLLER_ANNOTATION = "org.springframework.stereotype.Controller";
    private static final String REST_CONTROLLER_ANNOTATION = "org.springframework.web.bind.annotation.RestController";
    private static final String REQUEST_MAPPING_ANNOTATION = "org.springframework.web.bind.annotation.RequestMapping";
    private static final String GET_MAPPING_ANNOTATION = "org.springframework.web.bind.annotation.GetMapping";
    private static final String POST_MAPPING_ANNOTATION = "org.springframework.web.bind.annotation.PostMapping";
    private static final String PUT_MAPPING_ANNOTATION = "org.springframework.web.bind.annotation.PutMapping";
    private static final String DELETE_MAPPING_ANNOTATION = "org.springframework.web.bind.annotation.DeleteMapping";
    private static final String PATCH_MAPPING_ANNOTATION = "org.springframework.web.bind.annotation.PatchMapping";
    
    private final Map<String, List<ApiInfo>> projectCache = new ConcurrentHashMap<>();
    private final AtomicInteger scanProgress = new AtomicInteger(0);
    private volatile boolean cancelRequested = false;
    
    @Override
    public List<ApiInfo> scanProject(Project project) {
        if (project == null || project.isDisposed()) {
            return Collections.emptyList();
        }
        
        String projectKey = project.getName();
        List<ApiInfo> cachedResult = projectCache.get(projectKey);
        if (cachedResult != null) {
            return new ArrayList<>(cachedResult);
        }
        
        List<ApiInfo> allApis = new ArrayList<>();
        Module[] modules = ModuleManager.getInstance(project).getModules();
        
        for (Module module : modules) {
            if (cancelRequested) {
                break;
            }
            allApis.addAll(scanModule(module));
        }
        
        projectCache.put(projectKey, allApis);
        return allApis;
    }
    
    @Override
    public List<ApiInfo> scanModule(Module module) {
        if (module == null || module.isDisposed()) {
            return Collections.emptyList();
        }
        
        List<ApiInfo> apis = new ArrayList<>();
        Project project = module.getProject();
        
        // 扫描@Controller注解的类
        scanControllersByAnnotation(project, CONTROLLER_ANNOTATION, apis);
        
        // 扫描@RestController注解的类
        scanControllersByAnnotation(project, REST_CONTROLLER_ANNOTATION, apis);
        
        return apis;
    }
    
    private void scanControllersByAnnotation(Project project, String annotationName, List<ApiInfo> apis) {
        PsiClass annotationClass = JavaPsiFacade.getInstance(project)
                .findClass(annotationName, GlobalSearchScope.allScope(project));
        
        if (annotationClass == null) {
            return;
        }
        
        Collection<PsiClass> controllers = AnnotatedElementsSearch
                .searchPsiClasses(annotationClass, GlobalSearchScope.projectScope(project))
                .findAll();
        
        for (PsiClass controller : controllers) {
            if (cancelRequested) {
                break;
            }
            scanControllerClass(controller, apis);
        }
    }
    
    private void scanControllerClass(PsiClass controllerClass, List<ApiInfo> apis) {
        if (controllerClass == null) {
            return;
        }
        
        String baseMapping = getClassLevelMapping(controllerClass);
        PsiMethod[] methods = controllerClass.getMethods();
        
        for (PsiMethod method : methods) {
            if (cancelRequested) {
                break;
            }
            
            ApiInfo apiInfo = createApiInfo(method, controllerClass, baseMapping);
            if (apiInfo != null) {
                apis.add(apiInfo);
            }
        }
    }
    
    private String getClassLevelMapping(PsiClass controllerClass) {
        PsiAnnotation requestMapping = controllerClass.getAnnotation(REQUEST_MAPPING_ANNOTATION);
        if (requestMapping != null) {
            return extractMappingValue(requestMapping);
        }
        return "";
    }
    
    private ApiInfo createApiInfo(PsiMethod method, PsiClass controllerClass, String baseMapping) {
        String httpMethod = getHttpMethod(method);
        String path = getMethodPath(method, baseMapping);
        
        if (httpMethod == null || path == null) {
            return null;
        }
        
        ApiInfo apiInfo = new ApiInfo();
        apiInfo.setHttpMethod(httpMethod);
        apiInfo.setPath(path);
        apiInfo.setMethodName(method.getName());
        apiInfo.setClassName(controllerClass.getQualifiedName());
        apiInfo.setPackageName(getPackageName(controllerClass));
        apiInfo.setPsiMethod(method);
        apiInfo.setParameters(extractParameters(method));
        apiInfo.setReturnType(getReturnType(method));
        apiInfo.setAnnotations(extractAnnotations(method));
        apiInfo.setLastModified(System.currentTimeMillis());
        apiInfo.setFilePath(getFilePath(controllerClass));
        
        return apiInfo;
    }
    
    private String getHttpMethod(PsiMethod method) {
        if (method.hasAnnotation(GET_MAPPING_ANNOTATION) || 
            (method.hasAnnotation(REQUEST_MAPPING_ANNOTATION) && hasRequestMethod(method, "GET"))) {
            return "GET";
        }
        if (method.hasAnnotation(POST_MAPPING_ANNOTATION) || 
            (method.hasAnnotation(REQUEST_MAPPING_ANNOTATION) && hasRequestMethod(method, "POST"))) {
            return "POST";
        }
        if (method.hasAnnotation(PUT_MAPPING_ANNOTATION) || 
            (method.hasAnnotation(REQUEST_MAPPING_ANNOTATION) && hasRequestMethod(method, "PUT"))) {
            return "PUT";
        }
        if (method.hasAnnotation(DELETE_MAPPING_ANNOTATION) || 
            (method.hasAnnotation(REQUEST_MAPPING_ANNOTATION) && hasRequestMethod(method, "DELETE"))) {
            return "DELETE";
        }
        if (method.hasAnnotation(PATCH_MAPPING_ANNOTATION) || 
            (method.hasAnnotation(REQUEST_MAPPING_ANNOTATION) && hasRequestMethod(method, "PATCH"))) {
            return "PATCH";
        }
        
        // 默认为GET方法
        if (method.hasAnnotation(REQUEST_MAPPING_ANNOTATION)) {
            return "GET";
        }
        
        return null;
    }
    
    private boolean hasRequestMethod(PsiMethod method, String httpMethod) {
        PsiAnnotation annotation = method.getAnnotation(REQUEST_MAPPING_ANNOTATION);
        if (annotation == null) {
            return false;
        }
        
        PsiAnnotationMemberValue methodValue = annotation.findAttributeValue("method");
        if (methodValue != null) {
            String methodText = methodValue.getText();
            return methodText.contains(httpMethod);
        }
        
        return false;
    }
    
    private String getMethodPath(PsiMethod method, String baseMapping) {
        String methodMapping = "";
        
        // 检查各种映射注解
        PsiAnnotation[] annotations = {
            method.getAnnotation(REQUEST_MAPPING_ANNOTATION),
            method.getAnnotation(GET_MAPPING_ANNOTATION),
            method.getAnnotation(POST_MAPPING_ANNOTATION),
            method.getAnnotation(PUT_MAPPING_ANNOTATION),
            method.getAnnotation(DELETE_MAPPING_ANNOTATION),
            method.getAnnotation(PATCH_MAPPING_ANNOTATION)
        };
        
        for (PsiAnnotation annotation : annotations) {
            if (annotation != null) {
                methodMapping = extractMappingValue(annotation);
                break;
            }
        }
        
        // 组合基础路径和方法路径
        String fullPath = combinePaths(baseMapping, methodMapping);
        return fullPath.isEmpty() ? "/" : fullPath;
    }
    
    private String extractMappingValue(PsiAnnotation annotation) {
        PsiAnnotationMemberValue value = annotation.findAttributeValue("value");
        if (value == null) {
            value = annotation.findAttributeValue("path");
        }
        
        if (value != null) {
            String text = value.getText();
            // 移除引号和大括号
            text = text.replaceAll("[\"'{}]", "");
            return text.trim();
        }
        
        return "";
    }
    
    private String combinePaths(String basePath, String methodPath) {
        if (basePath == null) basePath = "";
        if (methodPath == null) methodPath = "";
        
        basePath = basePath.trim();
        methodPath = methodPath.trim();
        
        if (basePath.isEmpty()) {
            return methodPath.startsWith("/") ? methodPath : "/" + methodPath;
        }
        
        if (methodPath.isEmpty()) {
            return basePath.startsWith("/") ? basePath : "/" + basePath;
        }
        
        String base = basePath.startsWith("/") ? basePath : "/" + basePath;
        String method = methodPath.startsWith("/") ? methodPath : "/" + methodPath;
        
        if (base.endsWith("/")) {
            base = base.substring(0, base.length() - 1);
        }
        
        return base + method;
    }
    
    private List<ParamInfo> extractParameters(PsiMethod method) {
        List<ParamInfo> parameters = new ArrayList<>();
        PsiParameter[] psiParameters = method.getParameterList().getParameters();
        
        for (PsiParameter psiParam : psiParameters) {
            ParamInfo paramInfo = new ParamInfo();
            paramInfo.setName(psiParam.getName());
            paramInfo.setType(psiParam.getType().getCanonicalText());
            
            // 检查参数注解
            PsiAnnotation[] annotations = psiParam.getAnnotations();
            for (PsiAnnotation annotation : annotations) {
                String annotationName = annotation.getQualifiedName();
                if (annotationName != null) {
                    paramInfo.setAnnotation(getSimpleAnnotationName(annotationName));
                    
                    // 检查是否必需
                    if (annotationName.contains("RequestParam") || annotationName.contains("PathVariable")) {
                        PsiAnnotationMemberValue requiredValue = annotation.findAttributeValue("required");
                        if (requiredValue != null && "true".equals(requiredValue.getText())) {
                            paramInfo.setRequired(true);
                        }
                    }
                }
            }
            
            parameters.add(paramInfo);
        }
        
        return parameters;
    }
    
    private String getReturnType(PsiMethod method) {
        PsiType returnType = method.getReturnType();
        return returnType != null ? returnType.getCanonicalText() : "void";
    }
    
    private List<String> extractAnnotations(PsiMethod method) {
        List<String> annotations = new ArrayList<>();
        PsiAnnotation[] psiAnnotations = method.getAnnotations();
        
        for (PsiAnnotation annotation : psiAnnotations) {
            String qualifiedName = annotation.getQualifiedName();
            if (qualifiedName != null) {
                annotations.add(getSimpleAnnotationName(qualifiedName));
            }
        }
        
        return annotations;
    }
    
    private String getSimpleAnnotationName(String qualifiedName) {
        int lastDot = qualifiedName.lastIndexOf('.');
        return lastDot >= 0 ? qualifiedName.substring(lastDot + 1) : qualifiedName;
    }
    
    private String getPackageName(PsiClass psiClass) {
        PsiFile containingFile = psiClass.getContainingFile();
        if (containingFile instanceof PsiJavaFile) {
            return ((PsiJavaFile) containingFile).getPackageName();
        }
        return "";
    }
    
    private String getFilePath(PsiClass psiClass) {
        PsiFile containingFile = psiClass.getContainingFile();
        return containingFile != null ? containingFile.getVirtualFile().getPath() : "";
    }
    
    @Override
    public void refreshCache(Project project) {
        if (project != null) {
            projectCache.remove(project.getName());
        }
    }
    
    @Override
    public boolean isControllerClass(String className) {
        return className != null && 
               (className.contains("Controller") || className.contains("RestController"));
    }
    
    @Override
    public int getScanProgress() {
        return scanProgress.get();
    }
    
    @Override
    public void cancelScan() {
        cancelRequested = true;
    }
    
    /**
     * 异步扫描项目
     */
    public void scanProjectAsync(Project project, Runnable onComplete) {
        ProgressManager.getInstance().run(new Task.Backgroundable(project, "Scanning Controllers...", true) {
            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                cancelRequested = false;
                scanProgress.set(0);
                
                try {
                    // 在ReadAction中执行PSI访问操作
                    ReadAction.run(() -> {
                        scanProject(project);
                    });
                    scanProgress.set(100);
                } catch (Exception e) {
                    // 记录错误但不抛出异常
                } finally {
                    if (onComplete != null) {
                        ApplicationManager.getApplication().invokeLater(onComplete);
                    }
                }
            }
            
            @Override
            public void onCancel() {
                cancelScan();
            }
        });
    }
}